package com.zlc.workflow.flow.parser;

import com.zlc.common.common.exception.WorkflowException;
import com.zlc.common.common.service.ContextHolder;
import com.zlc.workflow.core.process.model.ProcessDeployEntity;
import com.zlc.workflow.flow.check.NodeCache;
import com.zlc.workflow.flow.errorcontext.DefaultErrorContext;
import com.zlc.workflow.flow.errorcontext.ErrorContext;
import com.zlc.workflow.flow.errorcontext.WindError;
import com.zlc.workflow.flow.errorcontext.WindLocalError;
import com.zlc.workflow.flow.node.ProcessModel;
import com.zlc.workflow.flow.node.StartNode;
import com.zlc.workflow.message.MessageHelper;
import com.zlc.workflow.utils.StreamHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.ObjectUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.nio.charset.StandardCharsets;

import static com.zlc.common.common.constant.WorkflowConstant.*;
import static com.zlc.workflow.constant.NodeType.START;
import static com.zlc.workflow.constant.ParserConstant.X_LOCAL;
import static com.zlc.workflow.constant.ParserConstant.Y_LOCAL;
import static com.zlc.workflow.message.ErrorCode.IO_ERROR;
import static com.zlc.workflow.message.ErrorCode.XML_EMPTY;
import static com.zlc.workflow.message.ErrorCode.XML_ERROR;
import static com.zlc.workflow.message.ErrorCode.XML_NO_START;


/**
 * 流程定义创建器
 *
 * @author zhanglei
 */
public class ProcessBuilder {

    private static Logger logger = LoggerFactory.getLogger(ProcessBuilder.class);

    /**
     * 流程定义流
     */
    private InputStream stream;


    /**
     * 流程定义二进制
     */
    private byte[]  bytes;

    /**
     * 流程定义字符串
     */
    private String  content;

    /**
     * 生成的流程定义xml内容
     */
    private String source;

    /**
     * 解析好后的流程定义对象  如果解析失败为null
     */
    private ProcessModel processModel;


    /**
     * 错误信息
     */
    private ErrorContext errorContent;

    /**
     * 构建是否成功
     */
    private boolean success = true;

    /**
     * 流程定义部署
     */
    private ProcessDeployEntity entity;

    public static final String DEPLOY_ENTITY = "DEPLOY_ENTITY";


    ProcessBuilder(InputStream stream){
        this.stream = stream;
    }

    ProcessBuilder(String content){
        this.content = content;
    }

    ProcessBuilder(String content,ProcessDeployEntity entity){
        this.content = content;
        this.entity = entity;
        ContextHolder.set(DEPLOY_ENTITY,entity);
    }

    ProcessBuilder(byte[] bytes){
        this.bytes = bytes;
    }

    private Document  buildDocument(){
        if(this.stream != null){
            this.source = XmlHelper.getStandardXml(StreamHelper.getStr(stream));
            return XmlHelper.buildDoc(this.source);
        }else if(this.content != null){
            this.source = XmlHelper.getStandardXml(content);
            return XmlHelper.buildDoc(content);
        }else if(this.bytes != null){
            String xml = new String(this.bytes, StandardCharsets.UTF_8);
            this.source = XmlHelper.getStandardXml(xml);
            return XmlHelper.buildDoc(this.source);
        }else {
            if(logger.isDebugEnabled()){
                logger.debug(MessageHelper.getMsg(XML_EMPTY));
            }
            WindError.error(XML_EMPTY,null);
        }
        return null;
    }


    public void  buildProcess(String processId){
        //构建document
        Document document = buildDocument();
        //构建流程定义
        try {
            initProcess(document, processId);
        }catch (Exception e){
            if(logger.isDebugEnabled()){
                logger.debug(e.getMessage());
            }
            logger.error(e.getMessage(),e);
            buildHalfwayErrorContent();
        }finally {
            clearCacheData();
        }
        //构建结束回收校验操作
        endOperate();
    }


    /**
     * 构建创建流程定义对象过程中存在的错误
     */
    private void  buildHalfwayErrorContent(){
        if(WindLocalError.get() != null){
            this.errorContent =  WindLocalError.get();
            findErrorXmlContent();
        }else {
            this.errorContent = new DefaultErrorContext()
                    .code(XML_ERROR);
            this.errorContent.processBuilderError()
                    .setErrorMsg(MessageHelper.getMsg(XML_ERROR))
                    .setContent(this.source);
        }
    }


    /**
     * 通过错误内容 从流程定义模型xml中找具体错误内容信息
     */
    private void  findErrorXmlContent(){
        String[] value = this.errorContent.processBuilderError().errorValue();
        if(!ObjectUtils.isEmpty(value)){
            StringReader reader = new StringReader(this.source);
            BufferedReader buf = new BufferedReader(reader);
            int numberLine = 0;
            while(true){
                try {
                    String line = buf.readLine();
                    numberLine++;
                    if(line == null){
                        break;
                    }
                    boolean exists = true;
                    for(String v : value){
                        exists = line.contains(v);
                    }
                    if(exists){
                        this.errorContent.processBuilderError().errorLine(numberLine)
                                .errorContent(line);
                        break;
                    }
                } catch (IOException e) {
                    logger.error(e.getMessage(),e);
                    WindLocalError.get()
                            .errorMsg("build process fail,because : "+e.getMessage())
                            .code(IO_ERROR);
                    break;
                }

            }
        }
        this.errorContent.processBuilderError().content(this.source);
    }


    private void initProcess(Document document,String processId){
        if(document != null) {
            Element element = document.getDocumentElement();

            String processName = element.getAttribute(PROCESS_NAME);
            String processCode = element.getAttribute(PROCESS_CODE);
            this.processModel = new ProcessModel(processId, processCode, processName);
            processModel.setElement(element);
            processModel.setNow(element);

            NodeList nodeList = element.getChildNodes();
            Element startElement = null;
            for (int i = 0; i < nodeList.getLength(); i++) {
                Node node = nodeList.item(i);
                if (node instanceof Element) {
                    String nodeName = node.getNodeName();
                    if (nodeName.equals(START.value())) {
                        startElement = (Element) node;
                    }
                }
            }
            checkStartNode(startElement);
            if(startElement != null) {
                StartNode startNode = initProcessStart(startElement, element);
                //设置流程子节点
                processModel.setChildNode(startNode);
            }
        }
    }

    /**
     * 构建流程定义开始节点
     * @param node      开始节点原始
     * @param element   流程定义所有元素
     */
    private StartNode initProcessStart(Element node,Element element){
        //构建开始节点
        String nodeId =  node.getAttribute(NODE_ID);
        String nodeName =  node.getAttribute(NODE_NAME);
        String nodeNum =  node.getAttribute(NODE_NUM);
        String x = node.getAttribute(X_LOCAL);
        String y = node.getAttribute(Y_LOCAL);
        double xNum = 0;
        double yNum = 0;
        try {
            xNum = Double.parseDouble(x);
            yNum = Double.parseDouble(y);
        }catch (Exception e){}
        StartNode startNode = new StartNode(nodeId, START.value(),nodeName);

        NodeCache.getInstance().put(nodeId,startNode);

        startNode.setNodeNum(nodeNum);
        startNode.setElement(element);
        startNode.setNow(node);
        startNode.setX(xNum);
        startNode.setY(yNum);
        startNode.setParentNode(processModel);
        startNode.build();
        return startNode;
    }

    private void  endOperate(){
        //打印错误信息
        printErrorMsg();
        //校验
        checkBuildErrorLocal();
    }


    private void  checkStartNode(Element startElement){
        if(startElement == null){
            this.processModel = null;
            this.success = false;
            if(logger.isDebugEnabled()){
                logger.debug(MessageHelper.getMsg(XML_NO_START));
                logger.debug(this.source);
            }
            WindError.error(XML_NO_START,null);
        }
    }

    private void checkBuildErrorLocal(){
        if(this.errorContent != null){
            this.success = false;
            this.processModel = null;
            throw new WorkflowException(errorContent.errorMsg());
        }
        if(WindLocalError.get().error()){
            this.processModel = null;
            this.success = false;
            this.errorContent =  WindLocalError.get();
            throw new WorkflowException(errorContent.errorMsg());
        }
    }

    private void printErrorMsg(){
        if(this.errorContent != null) {
            if (logger.isDebugEnabled()) {
                logger.debug("error xml content : "+this.errorContent.processBuilderError().content());
                logger.debug("error message: "+this.errorContent.errorMsg());
            }
        }
    }


    public void clearCacheData(){
        NodeCache.getInstance().remove();
    }

    public ErrorContext getErrorContent() {
        return errorContent;
    }

    public ProcessModel getProcessModel() {
        return processModel;
    }

    public boolean isSuccess() {
        return success;
    }
}
